package com.simple.datax.conn.impl;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.Properties;
import java.util.Vector;

import com.alibaba.dubbo.common.logger.Logger;
import com.alibaba.dubbo.common.logger.LoggerFactory;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import com.simple.datax.SimpleMessage;
import com.simple.datax.api.ConnectorException;
import com.simple.datax.conn.BaseConnector;

/** 
 * @Title: SFTPConnImpl.java 
 * @Package com.simple.datax.conn.impl 
 * @Description: 实现具体的SFTP连接操作，包括连接，接收，发送等。 
 * @author Jin Yi 
 * @date 2012-12-30
 * @version V1.0 
 */
public class SFTPConnImpl extends BaseConnector {
	
	private static final Logger logger = LoggerFactory
			.getLogger(SFTPConnImpl.class);
	public static final String TYPE = "SFTP";
	private String username;
	private String host;
	private String port="22";
	private String password;
	private ChannelSftp sftp;
	private String workingPath;
	private Session sshSession;
	private String trustMgr;
	private String knownHostFile;
	private long recvTotal;
	private long sendTotal;
	
	private static final String KNOWN_HOSTS = "../known_hosts";
	
	/**
	 * 构造器
	 * @param connPara
	 * @throws ConnectorException
	 */
	public SFTPConnImpl(String connPara) throws ConnectorException {
		super(connPara);
		this.connPara = connPara;
		parseConnPara();
		init();
	}
	
	/**
	 * 解析连接参数
	 * @throws ConnectorException 
	 */
	private void parseConnPara() throws ConnectorException {
		String[] tmpStr = splitConnPara(connPara);
		if (tmpStr.length==7) {
			host = tmpStr[0];
			if (tmpStr[1]!=null && !tmpStr[1].trim().isEmpty()) {
				port = tmpStr[1];
			}
			username = tmpStr[2];
			password = tmpStr[3];
			workingPath = tmpStr[4];
			trustMgr = tmpStr[5];
			knownHostFile = tmpStr[6];
		} else {
			throw new ConnectorException(
					"Parsing SFTPClient parameters failed. Please check parameter - "
							+ connPara);
		}
	}
	
	/**
	 * 初始化
	 * @throws ConnectorException
	 */
	private void init() throws ConnectorException {
		try {
            JSch jsch = new JSch();
            if (trustMgr.equalsIgnoreCase("YES")) {
            	jsch.setKnownHosts(KNOWN_HOSTS+File.separator+knownHostFile);
            }
            
            sshSession = jsch.getSession(username, host, Integer.parseInt(port));
            sshSession.setPassword(password);
            
            Properties sshConfig = new Properties();
            if (trustMgr.equalsIgnoreCase("YES")) {
            	sshConfig.put("StrictHostKeyChecking", "yes");
            } else {
            	sshConfig.put("StrictHostKeyChecking", "no");
            }
            sshSession.setConfig(sshConfig);
            sshSession.connect();
            Channel channel = sshSession.openChannel("sftp");
            channel.connect();
            sftp = (ChannelSftp) channel;
            changeWorkingDirectory(workingPath);
        } catch (Exception e) {
        	disconnect();
        	throw new ConnectorException("Init SFTP Connector failed - " + connPara, e);
        }
	}
	
	/**
	 * 检查SFTP client对象，并初始化
	 * @throws ConnectorException
	 */
	private void prepareSFTPClient() throws ConnectorException {
		
		if (sshSession != null && sshSession.isConnected() && sftp != null && sftp.isConnected()) {
			/**
			 * SFTPClient isConnected 函数在测试环境中可以正确返回当前连接状态，所以不需要下面的连接检测代码。但不清楚是否连接其他FTP服务器
			 * 都可以正确返回状态，所以保留下面的检测代码
			 */
//			//检测ftpClient是否还有效
//			try {
//				sftp.getHome(); // sftp 还有效就继续使用
//				return;
//			} catch(Exception e) {
//				try {
//					disconnect();
//				} catch (Exception ex) {
//					
//				}
//				logger.debug("SFTP Client needs re-init.");
//			}
			return;
			
		} else {
			disconnect();
			sshSession = null;
			sftp = null;
		}
		init();
	}

	@Override
	public String getMessage() throws ConnectorException {
		String ret = null;
		try {
			prepareSFTPClient();
			Vector<LsEntry> files = listFiles();
			
			if (files != null && files.size()>0) {
				for (LsEntry entry:files) {
					if (!entry.getAttrs().isDir()) {
						ret = download(entry.getFilename());
			            
			            // 如果文件正在上传过程中，则baos长度应该为0，此时，跳过此文件
						if (ret == null || ret.length()==0) {
							continue;
						}

						// 下载文件后，删除SFTP服务器上的原始文件
						delete(entry.getFilename());
						logger.debug("########Receiver Get Message: #" + ++recvTotal + " - " + connPara);
						break;
					}
				}
				
			}
		} catch (Exception ex) {
			logger.error("########Receiver Get Message Failed: #" + ++recvTotal + " - " + connPara);
			throw new ConnectorException("getMessage failed", ex);
		}
		return ret;
	}

	@Override
	protected void prepareSend() throws ConnectorException {
		prepareSFTPClient();
	}

	@Override
	protected void internalSend(SimpleMessage message)
			throws ConnectorException {
		try {
			upload(message.getOriFileName()+"."+message.getSentMsgFormat(), message.getContent());
			logger.debug("########Sender Sent Message: #" + ++sendTotal + " - " + connPara);
		} catch (SftpException ex) {
			logger.error("########Sender Sent Message Failed: #" + ++sendTotal + " - " + connPara);
			throw new ConnectorException("send message failed", ex, true);
		} catch (Exception ex) {
			logger.error("########Sender Sent Message Failed: #" + ++sendTotal + " - " + connPara);
			throw new ConnectorException("send message failed", ex);
		}
	}
	
	@Override
	public String getConnType() {
		return TYPE;
	}
	
	/**
	 * 将内容上传至服务器
	 * @param remoteFile
	 * @param content
	 * @throws FileNotFoundException
	 * @throws SftpException
	 * @throws ConnectorException
	 * @throws UnsupportedEncodingException
	 */
	private void upload(String remoteFile, String content) throws FileNotFoundException, SftpException, ConnectorException, UnsupportedEncodingException {
        if (remoteFile==null || remoteFile.isEmpty() || content==null) {
        	throw new ConnectorException("Remote file name is null or content is null.");
        }
        ByteArrayInputStream input = null;
        try {
        	input = new ByteArrayInputStream(content.getBytes(getEncoding())); 
        	sftp.put(input, remoteFile);
        } finally {
        	try {
				input.close();
			} catch (IOException e) {

			}
        }
    }

	
	/**
	 * 列举所有服务器当前目录下文件
	 * @return
	 * @throws SftpException
	 */
	@SuppressWarnings("unchecked")
	private Vector<LsEntry> listFiles() throws SftpException{
//		if (directory == null || directory.trim().isEmpty()) {
//			directory = sftp.pwd();
//		} else {
//			directory = ".";
//		}
        return sftp.ls(".");
    }
	
	/**
	 * 从服务器下载指定文件
	 * @param downloadFile
	 * @return String 返回文件内容
	 * @throws ConnectorException
	 */
	private String download(String downloadFile) throws ConnectorException {
		String ret = null;
		ByteArrayOutputStream baos = null;
        try {
        	baos = new ByteArrayOutputStream();
            sftp.get(downloadFile, baos);
            ret = baos.toString(getEncoding());

        } catch (Exception e) {
        	throw new ConnectorException("Failed to get file. ", e);
        } finally {
        	if (baos != null) {
				try {
					baos.close();
				} catch (IOException e) {

				}
			}
        }
        return ret;
    }
	
	/**
	 * 删除服务器文件
	 * @param deleteFile
	 * @throws ConnectorException
	 */
	public void delete(String deleteFile) throws ConnectorException {
        try {
            sftp.rm(deleteFile);
        } catch (Exception e) {
        	throw new ConnectorException("Failed to download file. ", e);
        }
    }
	
	/**
	 * 更改服务器当前目录
	 * @param directory
	 * @throws ConnectorException
	 */
	private void changeWorkingDirectory(String directory) throws ConnectorException {
		if (workingPath==null || workingPath.trim().isEmpty()) {
			return;
		}
		try {
            sftp.cd(directory);
        } catch (Exception e) {
        	throw new ConnectorException("Failed to change working directory - " + directory, e);
        }
	}
	
	@Override
	public void disconnect() {
		if (sshSession != null) {
			try {
				sshSession.disconnect();
			} catch (Exception ex) {
				// No nothing
			}
		}
		if (sftp != null) {
			try {
				sftp.disconnect();
			} catch (Exception ex) {
				// No nothing
			}
		}
		sshSession = null;
		sftp = null;
	}

	@Override
	protected void prepareReceive() throws ConnectorException {
		prepareSFTPClient();
	}
}
